
package com.jf.cloud.search.feign;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.jf.cloud.api.feign.SearchOrderRefundTaskFeignClient;
import com.jf.cloud.api.order.bo.EsOrderRefundBO;
import com.jf.cloud.api.vo.EsPageVO;
import com.jf.cloud.common.constant.Constant;
import com.jf.cloud.common.constant.SendTypeEnum;
import com.jf.cloud.common.exception.LuckException;
import com.jf.cloud.common.order.dto.OrderRefundSearchDTO;
import com.jf.cloud.common.order.dto.OrderSearchDTO;
import com.jf.cloud.common.order.vo.EsOrderVO;
import com.jf.cloud.common.order.vo.SendNotifyBO;
import com.jf.cloud.common.response.ResponseEnum;
import com.jf.cloud.common.response.ServerResponseEntity;
import com.jf.cloud.common.rocketmq.config.RocketMqConstant;
import com.jf.cloud.search.constant.EsConstant;
import com.jf.cloud.search.manager.OrderRefundSearchManager;
import com.jf.cloud.search.manager.OrderSearchManager;
import com.jf.cloud.search.util.EsSearchUtil;
import ma.glasnost.orika.MapperFacade;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.SearchHit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Hidden;

import java.util.*;

/**
 * 搜索符合定时任务条件的订单
 * @author zz
 * @date 2020/12/07
 */
@RestController
@Hidden
public class SearchOrderRefundTaskFeignController implements SearchOrderRefundTaskFeignClient {
    private static final Logger logger = LoggerFactory.getLogger(SearchOrderRefundTaskFeignController.class);

    private static final Integer CANAL_ORDER = 1;
    private static final Integer TIMEOUT_REMINDER = 2;

    @Autowired
    private OrderSearchManager orderSearchManager;
    @Autowired
    private OrderRefundSearchManager orderRefundSearchManager;
    @Autowired
    private RocketMQTemplate sendNotifyToUserTemplate;
    @Autowired
    private RocketMQTemplate orderRefundCancelMqTemplate;
    @Autowired
    private MapperFacade mapperFacade;
    @Override
    public ServerResponseEntity cancelWhenTimeOut() {
        //设置查询超时时间
        OrderRefundSearchDTO param = new OrderRefundSearchDTO();
        param.setEndTime(DateUtil.offsetDay(new Date(), -Constant.MAX_REFUND_APPLY_TIME).toJdkDate());
        param.setReturnMoneyStsList(new ArrayList<>(Arrays.asList(new Integer[]{1, 2, 3, 4})));
        param.setFetchSource(EsConstant.REFUND_ORDER_CANAL_TASK_FETCH_SOURCE);
        return handleOrder(param, CANAL_ORDER);
    }

    @Override
    public ServerResponseEntity pressProximityTimeOutOrder() {
        // 临时超时时间为 最大申请时间 - 12小时
        int overTime = Constant.MAX_REFUND_APPLY_TIME * 24;
        Date beginTime = DateUtil.offsetHour(new Date(), Constant.MAX_REFUND_HOUR - overTime).toJdkDate();
        Date endTime = DateUtil.offsetDay(new Date(), -Constant.MAX_REFUND_APPLY_TIME).toJdkDate();
        OrderRefundSearchDTO param = new OrderRefundSearchDTO();
        param.setBeginTime(beginTime);
        param.setEndTime(endTime);
        param.setReturnMoneyStsList(new ArrayList<>(Arrays.asList(new Integer[]{1, 2, 3, 4})));
        param.setFetchSource(EsConstant.REFUND_ORDER_TIMEOUT_REMINDER_TASK_FETCH_SOURCE);
        return handleOrder(param, TIMEOUT_REMINDER);
    }

    private ServerResponseEntity<Object> handleOrder(OrderRefundSearchDTO param, Integer type) {
        String scrollId = null;
        // 设置查询超时时间
        Scroll scroll = new Scroll(TimeValue.timeValueMinutes(5L));
        SearchRequest searchRequest = orderRefundSearchManager.buildScrollSearchRequest(param, scroll);
        try {
            // 进行第一次滚动查询
            SearchResponse searchResponse = EsSearchUtil.search(searchRequest);
            scrollId = searchResponse.getScrollId();
            /**
             *在这个位置已经读到了前一百条数据，可以在这先对这一百数据进行处理。下面滚动查询剩下的数据
             */
            sendOrderMq(searchResponse, type);
            //记录要滚动的ID
            //滚动查询部分，将从第1001笔数据开始取
            SearchHit[] hitsScroll = searchResponse.getHits().getHits();
            while (hitsScroll != null && hitsScroll.length > 0) {
                //构造滚动查询条件
                SearchScrollRequest searchScrollRequest = new SearchScrollRequest(scrollId);
                searchScrollRequest.scroll(scroll);
                searchResponse = EsSearchUtil.scroll(searchScrollRequest);
                scrollId = searchResponse.getScrollId();
                hitsScroll = searchResponse.getHits().getHits();
                // 发送消息
                sendOrderMq(searchResponse, type);
            }
        } catch (Exception e) {
            logger.error("退款订单定时任务异常: {}", e);
            return ServerResponseEntity.fail(ResponseEnum.EXCEPTION);
        } finally {
            if (Objects.nonNull(scrollId)) {
                //清除滚动，否则影响下次查询
                EsSearchUtil.clearScroll(scrollId);
            }
        }
        return ServerResponseEntity.success();
    }

    /**
     * 发送消息
     * @param searchResponse
     * @param type
     */
    private void sendOrderMq(SearchResponse searchResponse, Integer type) {
        if (Objects.equals(type, CANAL_ORDER)) {
            cancelWhenTimeOutMq(searchResponse);
        } else if (Objects.equals(type, TIMEOUT_REMINDER)) {
            pressProximityTimeOutOrderMq(searchResponse);
        }
    }

    private void pressProximityTimeOutOrderMq(SearchResponse searchResponse) {
        List<Long> orderIds = new ArrayList<>();
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            EsOrderRefundBO orderRefundBO = JSON.parseObject(hit.getSourceAsString(), EsOrderRefundBO.class);
            orderIds.add(orderRefundBO.getOrderId());
        }
        if (CollUtil.isEmpty(orderIds)) {
            return;
        }
        OrderSearchDTO orderSearchDTO = new OrderSearchDTO();
        orderSearchDTO.setOrderIds(orderIds);
        orderSearchDTO.setPageNum(1);
        orderSearchDTO.setPageSize(orderIds.size());
        orderSearchDTO.setFetchSource(null);
        EsPageVO<EsOrderVO> orderPage = orderSearchManager.pageSearchResult(orderSearchDTO);
        List<SendNotifyBO> sendNotifyList = new ArrayList<>();
        for (EsOrderVO esOrderVO : orderPage.getList()) {
            SendNotifyBO sendNotifyBO = mapperFacade.map(esOrderVO, SendNotifyBO.class);
            sendNotifyBO.setBizId(esOrderVO.getOrderId());
            sendNotifyBO.setSendType(SendTypeEnum.REFUND_OUT_TIME.getValue());
            sendNotifyBO.setHour(Constant.MAX_REFUND_APPLY_TIME);
            sendNotifyList.add(sendNotifyBO);
        }
        // 消息推送-退款临近超时
        SendStatus sendBizStatus = sendNotifyToUserTemplate.syncSend(RocketMqConstant.SEND_NOTIFY_TO_USER_TOPIC, new GenericMessage<>(sendNotifyList)).getSendStatus();
        if (!Objects.equals(sendBizStatus, SendStatus.SEND_OK)) {
            // 消息发不出去就抛异常，因为定时任务回调会有多次，几乎不可能每次都无法发送出去，发的出去无所谓因为接口是幂等的
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

    private void cancelWhenTimeOutMq(SearchResponse searchResponse) {
        List<Long> reufndIds = new ArrayList<>();
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            EsOrderRefundBO orderRefundBO = JSON.parseObject(hit.getSourceAsString(), EsOrderRefundBO.class);
            reufndIds.add(orderRefundBO.getOrderId());
        }
        if (CollUtil.isEmpty(reufndIds)) {
            return;
        }
        // 发送消息，订单支付成功
        SendStatus sendStatus = orderRefundCancelMqTemplate.syncSend(RocketMqConstant.ORDER_REFUND_CANAL_TOPIC, new GenericMessage<>(reufndIds)).getSendStatus();
        if (!Objects.equals(sendStatus, SendStatus.SEND_OK)) {
            // 消息发不出去就抛异常，因为定时任务回调会有多次，几乎不可能每次都无法发送出去，发的出去无所谓因为接口是幂等的
            throw new LuckException(ResponseEnum.EXCEPTION);
        }
    }

}
